// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: junglekim
#pragma once

#include <cmath>
#include <limits>
#include <string>

#include "coin2/exchange/base/symbology/product.h"
#include "coin2/strategy/order_executor/layering_executor3.h"
#include "coin2/strategy/util/moving_ohlc.h"
#include "coin2/strategy/vmm/vmm_pricer.h"
#include "coin2/strategy/vmm/vmm_util.h"
#include "presto/quant/math/moving_average.h"

using coin::proto::OrderDuration;
using coin::proto::OrderEvent;
using coin::proto::OrderSide;
using coin::proto::OrderType;
using coin::proto::TradeSide;
using coin2::app::impl::Driver;
using coin2::exchange::base::order::IOrderSubsystem;
using coin2::exchange::base::order::OrderSpec;
using coin2::exchange::base::order::OrderUpdate;
using coin2::exchange::base::symbology::IProduct;
using coin2::exchange::base::symbology::IProductInfo;
using coin2::strategy::order_executor::LayeringExecutor3;
using coin2::strategy::order_executor::LayeringLevels;
using coin2::strategy::util::MovingHigh;
using coin2::strategy::util::MovingLow;

namespace coin2::strategy::vmm {

static std::random_device rd;
static std::mt19937 gen = std::mt19937(rd());
static int64_t hour_ns = 3600e9;

inline std::string GetOptionSymbol(
    const nlohmann::json& contract_config,
    const std::string& rel_symbol,
    const std::string& option_type) {
  const auto& expiry = contract_config["expiry"].get<std::string>();
  std::vector<std::string> tmp;
  tmp.clear();
  boost::split(tmp, expiry, boost::is_any_of(" "));
  const auto expiry_ymd = tmp[0];
  tmp.clear();
  boost::split(tmp, expiry_ymd, boost::is_any_of("-"));
  const std::string expiry_res = boost::algorithm::join(tmp, "");
  const auto& strike_num = contract_config.contains("report_strike_price_num")
                               ? contract_config["report_strike_price_num"].get<std::string>()
                               : contract_config["strike_price_num"].get<std::string>();
  const auto& strike_denom = contract_config.contains("report_strike_price_denom")
                                 ? contract_config["report_strike_price_denom"].get<std::string>()
                                 : contract_config["strike_price_denom"].get<std::string>();
  tmp.clear();
  tmp = {rel_symbol, expiry_res, option_type, strike_num, strike_denom};
  const auto final_res = boost::algorithm::join(tmp, ".");
  return final_res;
}

inline double delta(double S, double K, double r, double T, double sigma) {
  double d1 = (std::log(S / K) + (r + 0.5 * (sigma * sigma)) * T) / (sigma * std::sqrt(T));
  return normalCDF(d1);
}

inline double gamma(double S, double K, double r, double T, double sigma) {
  constexpr double pi = 3.14159265358979323846;
  auto d1 = (std::log(S / K) + (r + std::pow(sigma, 2) / 2) * T) / (sigma * std::pow(T, 0.5));
  return std::exp(-0.5 * std::pow(d1, 2)) / (S * sigma * std::pow(2 * pi * T, 0.5));
}

inline double time2mat_yr(int64_t timestamp, int64_t expiry) {
  if (timestamp == expiry) {
    return 1e-6;
  }
  return (expiry - timestamp) / (365 * 1440 * 60e9);
}

inline int find_loc(double price, int num, double start, double stop) {
  return std::trunc((std::log10(price) - start) / ((stop - start) / (num - 1)));
}

inline int find_pos_loc(double pos, int logspace_num, std::vector<double> pos_vec, int p2p_flag) {
  if (pos > pos_vec[p2p_flag] && pos < pos_vec[p2p_flag + 1]) {
    return p2p_flag;
  } else {
    if (pos > pos_vec[p2p_flag]) {
      for (int i = p2p_flag + 1; i < logspace_num - 1; i++) {
        if (pos > pos_vec[i] && pos < pos_vec[i + 1]) {
          return i;
        }
      }
    } else {
      for (int i = p2p_flag - 1; i >= 0; i--) {
        if (pos > pos_vec[i] && pos < pos_vec[i + 1]) {
          return i;
        }
      }
    }
  }
  return logspace_num - 1;
}

struct LayeringParam {
  double start_bp;
  double end_bp;
  int layer_num;
  int random_timer_sec;
};

struct DeltaCalcParam {
  double logspace_start;
  double logspace_end;
  int logspace_num;
  double grid_period_hour;
  double real_delta_hour;
  double rfr;
  double volatility;
};

struct ExecutorParam {
  double qty_tolerance_bp;
  std::optional<double> qty_cancel_pct;
  bool is_randomised_post;
  int cancel2order_ratio;
};

struct CallInfo {
  std::vector<double>* strikes;
  std::vector<int64_t>* expiry_yrs;
  std::vector<int>* call_nums;
};

struct DeltaHedger {
  DeltaHedger(
      std::string symbol,
      CallInfo calls,
      LayeringParam layprm,
      DeltaCalcParam dcprm,
      ExecutorParam exeprm,
      double pda_pct,
      bool razor_on,
      int razor_num,
      int dh_agg_interval_sec,
      double dh_agg_max_quote,
      int window_sec,
      int bbo_interval_ms,
      double max_random_diff,
      int dh_agg_window_sec,
      double dh_agg_window_limit_quote)
      : symbol(symbol),
        strikes(*calls.strikes),
        expiry_yrs(*calls.expiry_yrs),
        call_nums(*calls.call_nums),
        min_spread_bp(layprm.start_bp * 2),
        bandwidth_bp(layprm.end_bp - layprm.start_bp),
        logspace_start(dcprm.logspace_start),
        logspace_end(dcprm.logspace_end),
        logspace_num(dcprm.logspace_num),
        layer_num(layprm.layer_num),
        qty_tolerance_bp(exeprm.qty_tolerance_bp),
        qty_cancel_pct(exeprm.qty_cancel_pct),
        grid_period_hour(dcprm.grid_period_hour),
        real_delta_hour(dcprm.real_delta_hour),
        rfr(dcprm.rfr),
        volatility(dcprm.volatility),
        pda_pct(pda_pct),
        razor_on(razor_on),
        razor_num(razor_num),
        dh_agg_interval_sec(dh_agg_interval_sec),
        dh_agg_max_quote(dh_agg_max_quote),
        is_randomised_post(exeprm.is_randomised_post),
        cancel2order_ratio(exeprm.cancel2order_ratio),
        random_timer_sec(layprm.random_timer_sec),
        dh_agg_window_sec(dh_agg_window_sec),
        dh_agg_window_limit_quote(dh_agg_window_limit_quote),
        bid(window_sec * 1e9),
        ask(window_sec * 1e9),
        dhagg(dh_agg_window_sec * 1e9),
        bbo_interval(bbo_interval_ms * 1e6) {
    S = logspace(logspace_start, logspace_end, logspace_num);
    random_dist = std::uniform_real_distribution<double>(1.0, 1.0 + max_random_diff);
  }

  void InitExecutor(const PassiveExecutorConfig& og_config, int64_t ts, Driver* driver) {
    driver_ = driver;
    auto deltahedger_config = new PassiveExecutorConfig();
    deltahedger_config->MergeFrom(og_config);
    deltahedger_config->set_filter_tag("0");
    deltahedger_config->set_accept_foreign_order(true);
    os =
        driver_->order()->GetUniqueSystem(MarketExchangeApi::FromString(deltahedger_config->mea()));
    auto get_product_holder = [this](const IProduct& product, int64_t timestamp) {
      return driver_->product_holder_manager()->GetProductHolder(product, timestamp);
    };
    executor = std::make_unique<LayeringExecutor3>(
        *deltahedger_config,
        os,
        ts,
        nullptr,
        get_product_holder,
        qty_tolerance_bp,
        std::nullopt,
        qty_cancel_pct,
        is_randomised_post,
        cancel2order_ratio);
  }

  void UpdateBbo(double bidp, double askp, int64_t ts) {
    CHECK_GT(bbo_interval, 0);
    if (ts - last_bbo_ts < bbo_interval) return;
    bid.Update(ts, bidp);
    ask.Update(ts, askp);
  }

  void UpdateFilledInfo(const OrderUpdate& upd) {
    auto oe = upd.product_order_info().event();
    if (oe.type() == coin::proto::OrderEvent::ORDER_FILLED) {
      if (oe.trade_side() == coin::proto::TRADE_BUY_SIDE && oe.fully_filled()) {
        if (oe.tag() == "0" || oe.tag() == "DeltaHedgeAgg") {
          double gamma_qty = Price2Gamma(oe.event_time(), oe.fill_price());
          vault += oe.fill_qty() - gamma_qty;
        }
      }
      if (oe.trade_side() == coin::proto::TRADE_SELL_SIDE && oe.fully_filled()) {
        if (oe.tag() == "0" || oe.tag() == "DeltaHedgeAgg") {
          double gamma_qty = Price2Gamma(oe.event_time(), oe.fill_price());
          vault -= oe.fill_qty() - gamma_qty;
        }
      }
      // deco filled
      if (oe.tag() != "0" && oe.tag() != "DeltaHedgeAgg") {
        if (oe.trade_side() == coin::proto::TRADE_BUY_SIDE) {
          vault += oe.fill_qty();
        }
        if (oe.trade_side() == coin::proto::TRADE_SELL_SIDE) {
          vault -= oe.fill_qty();
        }
      }
    }
  }

  std::pair<double, double> HedgeFairPrices(double real_pos) {
    double vault_midp = Pos2DesiredPrice(real_pos - vault);
    double fair_bid = vault_midp / (1 + 0.5 * min_spread_bp * 1e-4);
    double fair_ask = vault_midp * (1 + 0.5 * min_spread_bp * 1e-4);
    return {fair_bid, fair_ask};
  }

  std::pair<double, double> twap_bbo() const { return {bid.average(), ask.average()}; }

  void report(std::string symbol, int64_t ts, double desired_pos, double current_pos) {
    nlohmann::json j;
    auto& info = j["INFO"];
    info["SYMBOL"] = symbol;
    info["TIMESTAMP"] = ts;
    auto& report = j["POSITION_REPORT"];
    report["DESIRED_POSITION"] = desired_pos;
    report["CURRENT_POSITION"] = current_pos;
    SPDLOG_INFO(j.dump());
  }

  double Price2DesiredPos(int64_t ts, double price) {
    double desired_pos = 0.0;
    for (int i = 0; i < strikes.size(); ++i) {
      const int price_loc = find_loc(price, logspace_num, logspace_start, logspace_end);
      double mat = time2mat_yr(ts, expiry_yrs[i]);
      if (mat < 0) continue;
      if (time2mat_yr(ts + real_delta_hour * hour_ns, expiry_yrs[i]) < 0) {
        desired_pos -= call_nums[i] * delta(price, strikes[i], rfr, mat, volatility);
      } else {
        desired_pos -= call_nums[i] * BilinearInterpolation(
                                          delta1[i][price_loc],
                                          delta1[i][price_loc + 1],
                                          delta2[i][price_loc],
                                          delta2[i][price_loc + 1],
                                          T1[i],
                                          T2[i],
                                          S[price_loc],
                                          S[price_loc + 1],
                                          mat,
                                          price);
      }
    }
    return desired_pos;
  }

  double Pos2DesiredPrice(double pos) {
    int pos_loc = find_pos_loc(-pos, logspace_num, pos_vec, pos2price_flag);
    pos2price_flag = pos_loc;
    return (S[pos_loc] * (pos_vec[pos_loc + 1] + pos) +
            S[pos_loc + 1] * (-pos - pos_vec[pos_loc])) /
           (pos_vec[pos_loc + 1] - pos_vec[pos_loc]);
  }

  double Price2Gamma(int64_t ts, double price) {
    double acc_gamma = 0;
    for (int i = 0; i < strikes.size(); ++i) {
      const int price_loc = find_loc(price, logspace_num, logspace_start, logspace_end);
      double mat = time2mat_yr(ts, expiry_yrs[i]);
      if (mat < 0) continue;
      if (time2mat_yr(ts + real_delta_hour * hour_ns, expiry_yrs[i]) < 0) {
        acc_gamma += call_nums[i] * gamma(price, strikes[i], rfr, mat, volatility);
      } else {
        acc_gamma += call_nums[i] * BilinearInterpolation(
                                        gamma1[i][price_loc],
                                        gamma1[i][price_loc + 1],
                                        gamma2[i][price_loc],
                                        gamma2[i][price_loc + 1],
                                        T1[i],
                                        T2[i],
                                        S[price_loc],
                                        S[price_loc + 1],
                                        mat,
                                        price);
      }
    }
    return acc_gamma * gamma_grid;
  }

  void
  UpdateStatus(const int64_t ts, const double real_pos, const double midp, const double cb_qty) {
    if (last_midp == 0.0 || update_needed) {
      update_needed = true;
      last_ts = ts;
      last_midp = midp;

      if (last_grid != ts / (grid_period_hour * hour_ns)) {
        last_grid = ts / (grid_period_hour * hour_ns);
        double K;
        delta1.clear();
        delta2.clear();
        gamma1.clear();
        gamma2.clear();
        T1.clear();
        T2.clear();
        for (int i = 0; i < strikes.size(); ++i) {
          K = strikes[i];
          T1.push_back(time2mat_yr(ts - hour_ns, expiry_yrs[i]));
          T2.push_back(time2mat_yr(
              std::min(ts + (grid_period_hour + 1) * hour_ns, static_cast<double>(expiry_yrs[i])),
              expiry_yrs[i]));
          std::vector<double> d1, d2, g1, g2;
          for (auto& s : S) {
            d1.push_back(delta(s, K, rfr, T1[i], volatility));
            d2.push_back(delta(s, K, rfr, T2[i], volatility));
            g1.push_back(gamma(s, K, rfr, T1[i], volatility));
            g2.push_back(gamma(s, K, rfr, T2[i], volatility));
          }
          delta1.push_back(d1);
          delta2.push_back(d2);
          gamma1.push_back(g1);
          gamma2.push_back(g2);
        }
        for (int j = 0; j < logspace_num; j++) {
          double acc_pos = 0.0;
          for (int i = 0; i < strikes.size(); ++i) {
            acc_pos += delta1[i][j] * call_nums[i];
          }
          pos_vec.push_back(acc_pos);
        }
      }
      desired_pos = Price2DesiredPos(ts, midp);
      cur_pos = std::clamp(
          real_pos + cb_qty,
          desired_pos * (1 + pda_pct / 100),
          desired_pos * (1 - pda_pct / 100));
    }
    vault = vault_init ? vault : real_pos - desired_pos;  // initialise as neutral
    vault_init = true;
    dhagg.UpdateTimeOnly(ts);
    if (ts - last_report_ts > 30e9) {
      report(symbol, ts, desired_pos, real_pos);
      last_report_ts = ts;
    }
  }

  void UpdateSquad(
      const int64_t ts,
      const double bidp,
      const double askp,
      const double native_bidp,
      const double native_askp,
      const double arbi_bidp,
      const double arbi_askp) {
    std::vector<double> natives;
    natives.push_back(native_bidp);
    natives.push_back(native_askp);
    std::vector<double> arbis;
    arbis.push_back(arbi_bidp);
    arbis.push_back(arbi_askp);
    std::vector<std::pair<double, int>> fair_prices;
    fair_prices.push_back({bidp, 1});
    fair_prices.push_back({askp, -1});
    const auto& pi = &(executor->product_info());
    if (ts - last_seed_ts > random_timer_sec * 1e9) {
      random_seed = rand();
      last_seed_ts = ts;
    }
    for (const auto& [fair_price, side] : fair_prices) {
      std::vector<LayeringLevels::Level> levels;
      std::vector<double> start_end;
      const int idx = std::max(side, 0);
      start_end.push_back(fair_price * std::pow((1 + bandwidth_bp * 1e-4), -side));
      start_end.push_back(fair_price);
      std::vector<double> prices = order_executor::GridLinespacePrice(
          start_end[1 - idx],
          start_end[idx],
          layer_num,
          pi,
          side,
          random_seed);
      // set grid to calculate gamma qty
      gamma_grid = order_executor::getGrid(start_end[1 - idx], start_end[idx], layer_num, pi);
      const auto& dp = Price2DesiredPos(ts, prices.back());
      const auto& qty_sum = side * (dp - cur_pos);
      int razor_cnt = 0;
      double aggQty = 0;
      double aggPrice = 0;
      for (const auto& price : prices) {
        const auto& smq = pi->tick_qty().submittable_min_qty(price);
        double qty;
        if (razor_on) {
          qty = std::max(qty_sum / static_cast<double>(layer_num), smq);
          if (qty == smq) razor_cnt++;
        } else {
          qty = qty_sum / static_cast<double>(layer_num);
          if (qty < smq) continue;
        }
        qty *= random_dist(gen);
        if (side * price >= side * natives[idx]) {
          aggQty += qty;
          aggPrice = natives[idx];
        }
        if (razor_num >= razor_cnt) {
          if (side * price < side * arbis[idx])
            levels.push_back({LayeringLevels::POST, price, qty, std::nullopt, std::nullopt, "0"});
        }
      }
      if (aggQty > 0 && ts - last_hedge_agg_ts > dh_agg_interval_sec * 1e9) {
        last_hedge_agg_ts = ts;
        aggQty = std::min(dh_agg_max_quote / aggPrice, aggQty);
        if (dhagg.sum() + aggPrice * aggQty < dh_agg_window_limit_quote) {
          executor->ManageAggOrders(ts, side, aggPrice, aggQty, "DeltaHedgeAgg");
          dhagg.Update(ts, aggPrice * aggQty);
          SPDLOG_INFO("CHECK AGG SUM : {} / {}", dhagg.sum(), aggPrice * aggQty);
        } else {
          SPDLOG_INFO("CHECK AGG MAX VALUE TOUCHED!!");
        }
      }
      if (side == 1) buy_levels = levels;
      if (side == -1) sell_levels = levels;
    }
  }

  void SubmitLevels(const int64_t ts) {
    const auto& pi = &(executor->product_info());
    executor->UpdateState(ts);
    executor->ManageRisk(ts);
    executor->ManageSideOrders(ts, 1, LayeringLevels(pi, 1, buy_levels));
    executor->ManageSideOrders(ts, -1, LayeringLevels(pi, -1, sell_levels));
  }

  std::string symbol;
  std::vector<double> strikes;
  std::vector<int64_t> expiry_yrs;
  std::vector<int> call_nums;
  double min_spread_bp;
  double bandwidth_bp;
  double logspace_start;
  double logspace_end;
  int logspace_num;
  int layer_num;
  double qty_tolerance_bp;
  std::optional<double> qty_cancel_pct;
  double grid_period_hour;
  double real_delta_hour;
  double rfr;
  double volatility;
  double pda_pct;
  bool razor_on{true};
  int razor_num;
  int dh_agg_interval_sec;
  double dh_agg_max_quote;
  bool is_randomised_post;
  int cancel2order_ratio;
  int random_timer_sec;
  int dh_agg_window_sec;
  double dh_agg_window_limit_quote;
  presto::math::TimeWindowMovingAverage<double> bid{}, ask{}, dhagg{};
  int bbo_interval;
  std::vector<double> S;
  std::uniform_real_distribution<double> random_dist;
  double vault;
  double gamma_grid;
  bool vault_init{false};
  int pos2price_flag{0};
  double fair_bidp;
  double fair_askp;
  int64_t elastic_bid_ts{0};
  int64_t elastic_ask_ts{0};
  int64_t last_hedge_agg_ts{0};
  std::vector<std::vector<double>> delta1;
  std::vector<std::vector<double>> delta2;
  std::vector<std::vector<double>> gamma1;
  std::vector<std::vector<double>> gamma2;
  std::vector<double> pos_vec;
  std::vector<double> T1;
  std::vector<double> T2;
  int last_grid{0};
  int64_t last_bbo_ts{0};
  int64_t last_ts{0};
  double last_midp{0.0};
  double cur_pos;
  double desired_pos;
  int random_seed{0};
  int64_t last_seed_ts{0};
  int64_t last_report_ts{0};
  bool update_needed{false};
  std::vector<LayeringLevels::Level> buy_levels;
  std::vector<LayeringLevels::Level> sell_levels;
  Driver* driver_;
  std::unique_ptr<LayeringExecutor3> executor;
  IOrderSubsystem* os;
};

struct Decorator {
  Decorator(
      std::string deco_num,
      LayeringParam layprm,
      ExecutorParam exeprm,
      double value,
      double imbalance_scale,
      double twap_decay,
      double filled_decay,
      bool buy_on,
      bool sell_on,
      int duration_sec,
      int window_sec,
      double max_random_diff)
      : deco_num(deco_num),
        start_bp(layprm.start_bp),
        end_bp(layprm.end_bp),
        layer_num(layprm.layer_num),
        value(value),
        qty_tolerance_bp(exeprm.qty_tolerance_bp),
        qty_cancel_pct(exeprm.qty_cancel_pct),
        imbalance_scale(imbalance_scale),
        twap_decay(twap_decay),
        filled_decay(filled_decay),
        buy_on(buy_on),
        sell_on(sell_on),
        duration_sec(duration_sec),
        is_randomised_post(exeprm.is_randomised_post),
        cancel2order_ratio(exeprm.cancel2order_ratio),
        random_timer_sec(layprm.random_timer_sec),
        buy_pq(window_sec * 1e9),
        sell_pq(window_sec * 1e9) {
    random_dist = std::uniform_real_distribution<double>(1.0, 1.0 + max_random_diff);
  }

  void InitExecutor(const PassiveExecutorConfig& og_config, int64_t ts, Driver* driver) {
    driver_ = driver;
    auto decorator_config = new PassiveExecutorConfig();
    decorator_config->MergeFrom(og_config);
    decorator_config->set_filter_tag(deco_num);
    auto get_product_holder = [this](const IProduct& product, int64_t timestamp) {
      return driver_->product_holder_manager()->GetProductHolder(product, timestamp);
    };
    executor = std::make_unique<LayeringExecutor3>(
        *decorator_config,
        driver_->order()->GetUniqueSystem(MarketExchangeApi::FromString(decorator_config->mea())),
        ts,
        nullptr,
        get_product_holder,
        qty_tolerance_bp,
        std::nullopt,
        qty_cancel_pct,
        is_randomised_post,
        cancel2order_ratio);
  }

  double BuyPQ() const { return buy_pq.sum(); }
  double SellPQ() const { return sell_pq.sum(); }

  void UpdateImbalance(const double real_pos, const double desired_pos, const double cb_qty) {
    imbalance =
        std::clamp(((desired_pos - real_pos + cb_qty) / desired_pos) * imbalance_scale, -1.0, 1.0);
  }

  void UpdateTimeOnly(const int64_t ts) {
    buy_pq.UpdateTimeOnly(ts);
    sell_pq.UpdateTimeOnly(ts);
    if (bf_price.has_value() && bf_ts.has_value() && ts - bf_ts.value() > duration_sec * 1e9) {
      bf_price.reset();
      bf_ts.reset();
    }
    if (sf_price.has_value() && sf_ts.has_value() && ts - sf_ts.value() > duration_sec * 1e9) {
      sf_price.reset();
      sf_ts.reset();
    }
  }

  void UpdateTrade(const int64_t ts, double p, double q, coin::proto::TradeSide side) {
    if (side == coin::proto::TradeSide::TRADE_BUY_SIDE) {
      buy_pq.Update(ts, p * q);
      bf_ts = ts;
      const auto& current_orders = executor->GetBuyOrders();
      for (const auto& order : current_orders) {
        const auto& tag = order->tag();
        const auto& price = order->order_price();
        if (tag == deco_num && price < p) bf_price = price;
      }
    } else {
      sell_pq.Update(ts, p * q);
      sf_ts = ts;
      const auto& current_orders = executor->GetSellOrders();
      for (const auto& order : current_orders) {
        const auto& tag = order->tag();
        const auto& price = order->order_price();
        if (tag == deco_num && price > p) sf_price = price;
      }
    }
  }

  void
  UpdateSquad(const int64_t ts, const double bidp, const double askp, const double max_twap_diff) {
    double twap_decay_ = std::max(1.0, std::exp(std::min(twap_decay * (max_twap_diff - 1.0), 7.0)));
    std::vector<std::pair<double, int>> fair_prices;
    fair_prices.push_back({bidp, 1});
    fair_prices.push_back({askp, -1});
    const auto& pi = &(executor->product_info());
    const auto filled_pq = BuyPQ() + SellPQ();
    const double r = 1.0 - filled_decay / 2.0;
    CHECK(r >= 0 && r <= 1) << fmt::format("r must be in [0, 1]!!!, r: {}", r);
    const double filled_decay_ = std::max((value - filled_pq * r) / value, 0.0);
    if (ts - last_seed_ts > random_timer_sec * 1e9) {
      random_seed = rand();
      last_seed_ts = ts;
    }
    for (const auto& [fair_price, side] : fair_prices) {
      if (!(side == 1 && buy_on) && !(side == -1 && sell_on)) continue;
      std::vector<LayeringLevels::Level> levels;
      std::vector<double> start_end;
      start_end.push_back(fair_price * std::pow((1 + end_bp * 1e-4), -side));
      start_end.push_back(fair_price * std::pow((1 + start_bp * 1e-4), -side));
      double norm_sum = 0;
      std::vector<double> norms;
      std::vector<double> prices = order_executor::GridLinespacePrice(
          start_end[1 - std::max(side, 0)],
          start_end[std::max(side, 0)],
          layer_num,
          pi,
          side,
          random_seed);
      auto price_num = prices.size();
      for (int i = 0; i < price_num; ++i) {
        norms.push_back(random_dist(gen));
        norm_sum += norms.back();
      }
      for (int i = 0; i < price_num; ++i) {
        auto& price = prices[i];
        const auto& smq = pi->tick_qty().submittable_min_qty(price);
        if (side > 0 && sf_price.has_value() && sf_price.value() < price) continue;
        if (side < 0 && bf_price.has_value() && bf_price.value() > price) continue;
        double qty = value * (1 - side * imbalance) * filled_decay_ / twap_decay_ / price *
                     norms[i] / norm_sum;
        if (qty < smq) continue;
        levels.push_back({LayeringLevels::POST, price, qty, std::nullopt, std::nullopt, deco_num});
      }
      if (side == 1) buy_levels = levels;
      if (side == -1) sell_levels = levels;
    }
  }

  void SubmitLevels(const int64_t ts) {
    const auto& pi = &(executor->product_info());
    executor->UpdateState(ts);
    executor->ManageRisk(ts);
    executor->ManageSideOrders(ts, 1, LayeringLevels(pi, 1, buy_levels));
    executor->ManageSideOrders(ts, -1, LayeringLevels(pi, -1, sell_levels));
  }

  std::string deco_num;
  double start_bp;
  double end_bp;
  double layer_num;
  double value;
  double qty_tolerance_bp;
  std::optional<double> qty_cancel_pct;
  double imbalance_scale;
  double twap_decay;
  double filled_decay;
  bool buy_on;
  bool sell_on;
  int duration_sec;
  bool is_randomised_post;
  int cancel2order_ratio;
  int random_timer_sec;
  double imbalance;
  std::uniform_real_distribution<double> random_dist;
  presto::math::TimeWindowMovingAverage<double> buy_pq{}, sell_pq{};
  std::optional<double> bf_price;
  std::optional<double> sf_price;
  std::optional<int64_t> bf_ts;
  std::optional<int64_t> sf_ts;
  int random_seed{0};
  int64_t last_seed_ts{0};
  std::vector<LayeringLevels::Level> buy_levels;
  std::vector<LayeringLevels::Level> sell_levels;
  Driver* driver_;
  std::unique_ptr<LayeringExecutor3> executor;
};

struct MidpHint {
  MidpHint(
      int64_t start_time,
      std::optional<double> duration_sec,
      std::optional<double> midp_hint,
      bool forced)
      : start_time(start_time), duration_sec(duration_sec), midp_hint(midp_hint), forced(forced) {
    created = duration_sec.has_value() && midp_hint.has_value() ? true : false;
  }

  bool is_on(const int64_t ts) { return created && ts - start_time < duration_sec.value() * 1e9; }

  bool is_forced() const { return forced; }
  double get() const { return midp_hint.value(); }

  int64_t start_time;
  std::optional<double> duration_sec;
  std::optional<double> midp_hint;
  bool forced;
  bool created;
};

struct DelNeutAgg {
  DelNeutAgg(
      double order_freq_sec,
      double thold_bp,
      double max_bp,
      double max_quote,
      double pdcp,
      int book_num,
      double random_intensity,
      bool dry_run)
      : order_freq_sec(order_freq_sec),
        thold_bp(thold_bp),
        max_bp(max_bp),
        max_quote(max_quote),
        pdcp(pdcp),
        book_num(book_num),
        dry_run(dry_run) {
    CHECK_GE(thold_bp, max_bp);
    CHECK_GE(random_intensity, 1.0);
    random_dist = std::uniform_real_distribution<double>(1 / random_intensity, random_intensity);
  }

  void InitExecutor(const PassiveExecutorConfig& og_config, int64_t ts, Driver* driver) {
    driver_ = driver;
    auto delneut_config = new PassiveExecutorConfig();
    delneut_config->MergeFrom(og_config);
    delneut_config->set_filter_tag("DeltaNeutAgg");
    auto get_product_holder = [this](const IProduct& product, int64_t timestamp) {
      return driver_->product_holder_manager()->GetProductHolder(product, timestamp);
    };
    os = driver_->order()->GetUniqueSystem(MarketExchangeApi::FromString(delneut_config->mea()));
    executor = std::make_unique<LayeringExecutor3>(
        *delneut_config,
        os,
        ts,
        nullptr,
        get_product_holder,
        std::nullopt,
        std::nullopt,
        std::nullopt,
        false,
        std::numeric_limits<int>::max() - 1);
  }

  bool Update(const int64_t& ts, const VmmPriceResult& result, DeltaHedger* deltahedger) {
    const auto& [twap_bid, twap_ask] = deltahedger->twap_bbo();
    const double& cur_pos = deltahedger->cur_pos;
    const double& desired_pos = deltahedger->desired_pos;
    const auto& random_intensity = random_dist(gen);
    if (ts - last_order_ts < random_intensity * order_freq_sec * 1e9) return false;
    const auto& bidp = result.native_bid.first.value();
    const auto& askp = result.native_ask.first.value();
    const auto& book = result.bookbuilder;
    if ((cur_pos > desired_pos && bidp / twap_bid - 1.0 > thold_bp * 1e-4) ||
        (cur_pos < desired_pos && twap_ask / askp - 1.0 > thold_bp * 1e-4)) {
      const int side = cur_pos > desired_pos ? -1 : 1;
      int bookN = side == 1 ? std::min(book_num, static_cast<int>(book->AskSize()))
                            : std::min(book_num, static_cast<int>(book->BidSize()));
      double max_price = side == 1 ? askp * (1.0 + max_bp * 1e-4) : bidp * (1.0 + max_bp * 1e-4);
      const double pdcqt = (desired_pos - cur_pos) * side * pdcp * 0.01;
      const double imprice =
          side == 1 ? InnermostOrderPrice(os).first ? *InnermostOrderPrice(os).first : 0.0
                    : InnermostOrderPrice(os).second ? *InnermostOrderPrice(os).second : 0.0;
      double order_price = 0.0, order_qty = 0.0, tmp_quote = 0.0, final_desired_pos = 0.0;
      for (int i = 0; i < bookN; ++i) {
        auto book_price = side == 1 ? book->AskN(i)->price : book->BidN(i)->price;
        auto book_qty = side == 1 ? book->AskN(i)->qty : book->BidN(i)->qty;
        if (imprice != 0.0 &&
            (side * book_price >= side * imprice || side * book_price >= side * max_price ||
             order_executor::IsClose(book_price, imprice)))
          break;
        order_price = book_price;
        const double tmp_desired_pos = deltahedger->Price2DesiredPos(ts, book_price);
        double tmp_qty = 0.0;
        if (order_qty + book_qty >= pdcqt) tmp_qty = pdcqt - order_qty;
        if (tmp_quote + book_price * book_qty >= max_quote)
          tmp_qty = tmp_qty > 0.0 ? std::min((max_quote - tmp_quote) / book_price, tmp_qty)
                                  : (max_quote - tmp_quote) / book_price;
        if (side * cur_pos + (order_qty + book_qty) >= side * tmp_desired_pos)
          tmp_qty = tmp_qty > 0.0
                        ? std::min(tmp_qty, side * tmp_desired_pos - side * cur_pos - order_qty)
                        : side * (tmp_desired_pos - side * cur_pos - order_qty);
        final_desired_pos = tmp_desired_pos;
        if (tmp_qty == 0.0) {
          order_qty += book_qty;
          tmp_quote += book_price * book_qty;
        } else {
          order_qty += tmp_qty;
          break;
        }
      }
      if (order_price == 0.0 || order_qty == 0.0) return false;
      order_qty = std::min(order_qty * random_intensity, side * (final_desired_pos - cur_pos));
      const double smq = executor->product_info().tick_qty().submittable_min_qty(order_price);
      order_qty = std::max(smq, order_qty);
      if (!dry_run) executor->ManageAggOrders(ts, side, order_price, order_qty, "DeltaNeutAgg");
      last_order_ts = ts;
      return true;
    } else {
      return false;
    }
  }

  double order_freq_sec;
  double thold_bp;
  double max_bp;
  double max_quote;
  double pdcp;
  int book_num;
  bool dry_run;
  bool update_needed{false};
  std::uniform_real_distribution<double> random_dist;
  int64_t last_order_ts;
  Driver* driver_;
  IOrderSubsystem* os;
  std::unique_ptr<LayeringExecutor3> executor;
};
}  // namespace coin2::strategy::vmm